﻿/* 11. *** Implement the "Falling Rocks" game in the text console. 
         * A small dwarf stays at the bottom of the screen and can move left and right (by the arrows keys). 
         * A number of rocks of different sizes and forms constantly fall down and you need to avoid a crash. 
         * Rocks are the symbols ^, @, *, &, +, %, $, #, !, ., ;, - distributed with appropriate density. 
         * The dwarf is (O). 
         * Ensure a constant game speed by Thread.Sleep(150). 
         * Implement collision detection and scoring system. */

using System;

public class FallingRocks
{
    #region Variables

    #region Console
    public static int windowHeight = Console.WindowHeight;
    public static int windowWidth = Console.WindowWidth;
    public static int consoleBackgroundColor;
    public static int consoleForegroundColor;
    #endregion

    #region Random
    public static Random randomGenerator = new Random();
    #endregion

    #region Player
    public static bool checkForCrash;
    public static int playerLives;
    public static string playerBody;
    public static int playerBodyLength;
    public static int playerBackgroundColor;
    public static int playerForegroundColor;
    public static int startPlayerPositionX;
    public static int startPlayerPositionY;
    public static int playerPositionX;
    public static int playerPositionY;
    public static long scores;
    #endregion

    #region Arena
    public static char[,] arenaRocks = new char[windowHeight, windowWidth];
    public static int[,] arenaRocksColor = new int[windowHeight, windowWidth];
    public static int[] rowDifficulty = new int[windowHeight];
    //public static int partialDifficulty;
    #endregion

    #region Rocks
    public static int maxStructuresInRow;
    public static int maxRocksInStructure;
    public static int columnOfNewRow;
    public static int isThereRockOnRow;
    public static int structuresInRow;
    public static int fieldOfRow;
    public static int rocksInStructure;
    public static char rockElementInStructure;
    public static int structureColor;
    public static int startPositionOfRockStructure;
    public static int counter;
    #endregion

    #region Action
    public static int startSpeed;
    public static int speed;
    public static DateTime speedTimer;
    public static int accelerating; //per second
    public static DateTime acceleratingTimer;
    //public static int windSpeed;
    //public static int windDuration;
    //public static int bestResult;
    #endregion

    #region Checks
    public static bool allowableUserSettings;
    #endregion

    #endregion

    public static void Main()
    {
        UserSettings();
        if (IsAllowableUserSettings())
        {
            StartSettings();
            LivesLeft();
            SetStartingPosition();
            speedTimer = DateTime.Now;
            acceleratingTimer = DateTime.Now;

            while (playerLives > 0)
            {
                #region Pushed Button

                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo pushedButton = Console.ReadKey();

                    //SetEmptyBodyAtPosition(playerPositionX, playerPositionY, new string(' ', playerBody.Length));
                    //Xor
                    PrintPlayerReleasedTrail();

                    if (pushedButton.Key == ConsoleKey.LeftArrow)
                        PlayerMoveLeft();
                    else if (pushedButton.Key == ConsoleKey.RightArrow)
                        PlayerMoveRight();
                    else if (pushedButton.Key == ConsoleKey.UpArrow)
                        PlayerMoveUp();
                    else if (pushedButton.Key == ConsoleKey.DownArrow)
                        PlayerMoveDown();
                    else if (pushedButton.Key == ConsoleKey.P)
                    {
                        PrintPlayer();
                        Console.ReadKey();
                    }
                }

                #endregion

                CheckForCrash();
                PrintPlayer();

                if ((DateTime.Now >= acceleratingTimer.AddSeconds(1)) && (speed > accelerating))
                {
                    speed -= accelerating;
                    acceleratingTimer = DateTime.Now;
                }

                if (DateTime.Now >= speedTimer.AddMilliseconds(speed))
                {
                    moveRocks();
                    scores += rowDifficulty[windowHeight - 1] * (3 * (windowHeight - startPlayerPositionY - 1) + 2 * startSpeed / speed);
                    PrintRocks();
                    PrintScores();
                    speedTimer = DateTime.Now;
                }
            }
        }
        else
            GameOver();
    }

    #region Checks

    public static bool IsAllowableUserSettings()
    {
        allowableUserSettings = true;
        allowableUserSettings &= ((0 <= consoleBackgroundColor) && (consoleBackgroundColor <= 15));
        allowableUserSettings &= ((0 <= consoleForegroundColor) && (consoleForegroundColor <= 15));
        allowableUserSettings &= (0 < playerLives);
        allowableUserSettings &= ((playerBody != "") && (playerBody != " "));
        allowableUserSettings &= ((0 <= playerBackgroundColor) && (playerBackgroundColor <= 15));
        allowableUserSettings &= ((0 <= playerForegroundColor) && (playerForegroundColor <= 15));
        allowableUserSettings &= (0 < maxStructuresInRow);
        allowableUserSettings &= (0 < maxRocksInStructure);
        allowableUserSettings &= (maxStructuresInRow * maxRocksInStructure < windowWidth / 2);
        allowableUserSettings &= (0 < startSpeed);
        //allowableUserSettings &= (0 < accelerating);

        return allowableUserSettings;
    }

    public static void CheckForCrash()
    {
        checkForCrash = false;

        for (int length = 0; length < playerBodyLength; length++)
            if (arenaRocks[playerPositionY, playerPositionX + length] != ' ')
            {
                checkForCrash = true;
                break;
            }

        if (checkForCrash)
        {
            playerLives--;

            if (playerLives > 0)
            {
                LivesLeft();
                SetStartingPosition();
            }
            else
                GameOver();
        }
    }

    public static void LivesLeft()
    {
        Console.Clear();
        PrintScores();
        PrintBody(windowWidth / 2 - 20, windowHeight / 2, 0, 3,
            string.Format("LIVES Left  {0}\tPress any key to Start...", playerLives));
        Console.ReadKey();
        Console.Clear();
    }

    public static void GameOver()
    {
        Console.Clear();
        PrintScores();
        PrintBody(windowWidth / 2 - 5, windowHeight / 2, 0, 2, string.Format("Game Over!"));
    }

    #endregion

    #region Preparations

    public static void UserSettings()
    {
        consoleBackgroundColor = 0;
        consoleForegroundColor = 15;
        startPlayerPositionX = windowWidth / 2;
        startPlayerPositionY = (windowHeight - 1) / 2;

        playerLives = 5;

        playerBody = "^!^";
        playerBackgroundColor = 0;
        playerForegroundColor = 15;

        maxStructuresInRow = 5;
        maxRocksInStructure = 2;

        isThereRockOnRow = 80;

        startSpeed = 150;
        accelerating = 4;
        //windSpeed = 1;
        //windDuration = 5;
    }

    public static void StartSettings()
    {
        // 80x25, but from 0 to 79x24                        
        Console.BufferWidth = windowWidth;
        Console.BufferHeight = windowHeight;
        Console.Clear();
        Console.CursorVisible = false;
        Console.BackgroundColor = (ConsoleColor)consoleBackgroundColor;
        Console.ForegroundColor = (ConsoleColor)consoleForegroundColor;

        playerBodyLength = playerBody.Length;

        maxStructuresInRow++;
        maxRocksInStructure++;

        scores = 0;
    }

    public static void SetStartingPosition()
    {
        playerPositionX = startPlayerPositionX;
        playerPositionY = startPlayerPositionY;
        PrintPlayer();

        for (int row = 0; row < windowHeight; row++)
        {
            rowDifficulty[row] = 0;
            for (int column = 0; column < windowWidth; column++)
            {
                arenaRocks[row, column] = ' ';
                arenaRocksColor[row, column] = 0;
            }
        }

        speed = startSpeed;
    }

    #endregion

    #region Moves

    #region Player

    public static void PlayerMoveRight()
    {
        if ((playerPositionX + playerBodyLength < windowWidth - 2)
                || ((playerPositionX + playerBodyLength < windowWidth) && (playerPositionY < windowHeight - 2))
                    || ((playerPositionX + playerBodyLength < windowWidth - 1) && (playerPositionY < windowHeight - 1)))
            playerPositionX++;
    }

    public static void PlayerMoveLeft()
    {
        if (playerPositionX > 0)
            playerPositionX--;
    }

    public static void PlayerMoveDown()
    {
        if ((playerPositionY < windowHeight - 3)
                || ((playerPositionY == windowHeight - 3) && (playerPositionX + playerBodyLength < windowWidth))
                    || ((playerPositionY == windowHeight - 2) && (playerPositionX + playerBodyLength < windowWidth - 1)))
            playerPositionY++;
    }

    public static void PlayerMoveUp()
    {
        if (playerPositionY > 0)
            playerPositionY--;
    }

    #endregion

    #region Rocks

    public static void NewRocksRow()
    {
        rowDifficulty[0] = 0;

        if (randomGenerator.Next(1, 101) <= isThereRockOnRow)
        {
            columnOfNewRow = 0;
            structuresInRow = randomGenerator.Next(1, maxStructuresInRow);
            fieldOfRow = windowWidth / structuresInRow;

            for (int structure = 0; structure < structuresInRow; structure++)
            {
                rocksInStructure = randomGenerator.Next(1, maxRocksInStructure);
                rowDifficulty[0] += rocksInStructure;
                rockElementInStructure = RockElement();
                structureColor = randomGenerator.Next(1, 15);

                if (structure == structuresInRow - 1)
                    fieldOfRow += windowWidth % structuresInRow;

                startPositionOfRockStructure = randomGenerator.Next(0, (fieldOfRow + 1 - rocksInStructure));

                for (counter = 1; counter <= startPositionOfRockStructure; counter++)
                {
                    arenaRocksColor[0, columnOfNewRow] = 0;
                    arenaRocks[0, columnOfNewRow] = ' ';
                    columnOfNewRow++;
                }

                for (counter = 1; counter <= rocksInStructure; counter++)
                {
                    arenaRocksColor[0, columnOfNewRow] = structureColor;
                    arenaRocks[0, columnOfNewRow] = rockElementInStructure;
                    columnOfNewRow++;
                }

                for (counter = 1; counter <= fieldOfRow - startPositionOfRockStructure - rocksInStructure; counter++)
                {
                    arenaRocksColor[0, columnOfNewRow] = 0;
                    arenaRocks[0, columnOfNewRow] = ' ';
                    columnOfNewRow++;
                }
            }
        }
        else
            for (counter = 0; counter < windowWidth; counter++)
            {
                arenaRocksColor[0, counter] = 0;
                arenaRocks[0, counter] = ' ';
            }
    }

    public static void moveRocks()
    {
        for (int row = windowHeight - 1; row > 0; row--)
            for (int column = 0; column < windowWidth; column++)
            {
                arenaRocksColor[row, column] = arenaRocksColor[row - 1, column];
                arenaRocks[row, column] = arenaRocks[row - 1, column];
                rowDifficulty[row] = rowDifficulty[row - 1];
            }

        NewRocksRow();
    }

    #endregion

    #endregion

    #region Print

    public static void SetEmptyBodyAtPosition(int x, int y, string body)
    {
        PrintBody(x, y, consoleBackgroundColor, consoleForegroundColor, body);
    }

    public static void PrintBody(int x, int y, int backgrColor, int foregrColor, string body)
    {
        Console.SetCursorPosition(x, y);
        Console.BackgroundColor = (ConsoleColor)backgrColor;
        Console.ForegroundColor = (ConsoleColor)foregrColor;
        Console.Write(body);
    }

    public static void PrintPlayer()
    {
        //PrintBody(playerPositionX, playerPositionY, playerBackgroundColor, playerForegroundColor, playerBody);
        Console.SetCursorPosition(windowWidth - 2, windowHeight - 1);
        Console.BackgroundColor = (ConsoleColor)3;
        Console.ForegroundColor = (ConsoleColor)3;
        Console.Write(" ");
        Console.SetCursorPosition(windowWidth - 1, windowHeight - 2);
        Console.Write(" ");

        Console.SetCursorPosition(playerPositionX, playerPositionY);
        Console.BackgroundColor = (ConsoleColor)playerBackgroundColor;
        Console.ForegroundColor = (ConsoleColor)playerForegroundColor;
        Console.Write(playerBody);
        //Console.SetCursorPosition(playerPositionX, playerPositionY);
    }

    public static void PrintPlayerReleasedTrail()
    {
        Console.SetCursorPosition(playerPositionX, playerPositionY);
        Console.BackgroundColor = (ConsoleColor)consoleBackgroundColor;
        for (int column = playerPositionX; column < playerPositionX + playerBodyLength; column++)
        {
            Console.ForegroundColor = (ConsoleColor)arenaRocksColor[playerPositionY, column];
            Console.Write(arenaRocks[playerPositionY, column]);
        }
    }

    public static char RockElement()
    {
        switch (randomGenerator.Next(1, 16))
        {
            case 1: return '^';
            case 2: return '@';
            case 3: return '*';
            case 4: return '&';
            case 5: return '+';
            case 6: return '%';
            case 7: return '$';
            case 8: return '#';
            case 9: return '!';
            case 10: return '.';
            case 11: return ';';
            case 12: return ';';
            case 13: return '-';
            case 14: return '=';
            default: return '?';
        }
    }

    public static void PrintRocks()
    {
        for (int row = windowHeight - 1; row >= 0; row--)
        {
            int startColumn = 0;
            int endColumn = 0;

            for (int i = 0; i < 5; i++)
            {
                PrintPlayer();

                if (i < 4)
                    endColumn += windowWidth / 5;
                else
                    endColumn = windowWidth - 1;

                Console.SetCursorPosition(startColumn, row);
                //Console.BackgroundColor = (ConsoleColor)consoleBackgroundColor;
                for (; startColumn < endColumn; startColumn++)
                {
                    Console.ForegroundColor = (ConsoleColor)arenaRocksColor[row, startColumn];
                    Console.Write(arenaRocks[row, startColumn]);
                }
            }
        }
    }

    public static void PrintScores()
    {
        PrintBody(0, 0, consoleBackgroundColor, consoleForegroundColor, string.Format("Scores: {0}", scores));
    }

    #endregion
}